﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Pfz.DataTypes;
using System.Runtime.Serialization;

namespace Pfz.Caching
{
	/// <summary>
	/// This class is a GCHandle wrapper that allows you to reference other objects
	/// using WeakReference, Strong or other reference types.
	/// This class is thread and abort-safe, but uses lock(this) in its methods, 
	/// so never hold a lock over a reference, as this can cause dead-locks.
	/// </summary>
	[Serializable]
	public sealed class Reference<T>:
		IReference<T>,
		IEquatable<IReference<T>>,
		ISerializable,
		#if !WINDOWS_PHONE
			ICloneable<Reference<T>>,
		#endif
		IDisposable
	where
		T: class
	{
		private ThreadUnsafeReferenceSlim<T> _reference;

		/// <summary>
		/// Creates a new empty reference.
		/// </summary>
		public Reference():
			this(default(T), GCHandleType.Weak)
		{
		}
		
		/// <summary>
		/// Creates a new reference using the given value and handle type.
		/// </summary>
		public Reference(T value, GCHandleType handleType=GCHandleType.Weak)
		{
			_reference = new ThreadUnsafeReferenceSlim<T>(value, handleType);
		}
		
		/// <summary>
		/// Frees the handle.
		/// </summary>
		~Reference()
		{
			_reference.Dispose();
		}
		
		/// <summary>
		/// Frees the handle immediatelly.
		/// </summary>
		public void Dispose()
		{
			lock(this)
				_reference.Dispose();
			
			GC.SuppressFinalize(this);
		}
		
		/// <summary>
		/// Gets or sets the value pointed by the handle.
		/// </summary>
		public T Value
		{
			get
			{
				lock(this)
					return _reference.Value;
			}
			set
			{
				lock(this)
					_reference.Value = value;
			}
		}
		
		/// <summary>
		/// Gets or sets the handle type.
		/// </summary>
		public GCHandleType HandleType
		{
			get
			{
				lock(this)
					return _reference.HandleType;
			}
			set
			{
				lock(this)
					_reference.HandleType = value;
			}
		}

		/// <summary>
		/// Sets the data of this reference.
		/// </summary>
		public void Set(GCHandleType handleType, T value)
		{
			lock(this)
				_reference.Set(handleType, value);
		}

		/// <summary>
		/// Gets the data of this reference.
		/// </summary>
		public ReferenceData<T> GetData()
		{
			lock(this)
				return _reference.GetData();
		}

		/// <summary>
		/// Gets the HashCode of the Value.
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			return _reference.GetHashCode();
		}

		/// <summary>
		/// Compares the Value of this reference with the value of another reference.
		/// </summary>
		public override bool Equals(object obj)
		{
			return _reference.Equals(obj);
		}

		/// <summary>
		/// Compares the Value of this reference with the value of another reference.
		/// </summary>
		public bool Equals(IReference<T> other)
		{
			return _reference.Equals(other);
		}

		#region IValueContainer Members
			object IValueContainer.Value
			{
				get
				{
					lock(this)
						return _reference.Value;
				}
				set
				{
					lock(this)
						_reference.Value = (T)value;
				}
			}
		#endregion
		#region ICloneable<Reference<T>> Members
			/// <summary>
			/// Creates a copy of this reference.
			/// </summary>
			[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
			public Reference<T> Clone()
			{
				lock(this)
					return new Reference<T>(_reference.Value, _reference.HandleType);
			}
		#endregion
		#region ICloneable Members
			#if !WINDOWS_PHONE
			object ICloneable.Clone()
			{
				return Clone();
			}
			#endif
		#endregion

		#region IReference Members
			void IReference.Set(GCHandleType handleType, object value)
			{
				Set(handleType, (T)value);
			}
			IReferenceData IReference.GetData()
			{
				return GetData();
			}
		#endregion
		#region ISerializable
			private Reference(SerializationInfo info, StreamingContext context)
			{
				info.AddValue("type", HandleType);

				var value = Value;
				if (value != null)
				{
					switch(HandleType)
					{
						case GCHandleType.Normal:
						case GCHandleType.Pinned:
							info.AddValue("Value", Value);
							break;
					}
				}
			}
			void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
			{
				var type = (GCHandleType)info.GetValue("type", typeof(GCHandleType));
				T value = null;
				if (info.MemberCount == 2)
					value = (T)info.GetValue("Value", typeof(T));

				Set(type, value);
			}
		#endregion
	}
}
